﻿using Rubberduck.Parsing.Rewriter;
using Rubberduck.Parsing.Symbols;
using Rubberduck.Parsing.VBA;
using Rubberduck.Refactorings.Common;
using Rubberduck.Resources;
using System;
using System.Diagnostics;
using System.Linq;
using Rubberduck.Refactorings.EncapsulateField;
using System.Collections.Generic;
using Rubberduck.SmartIndenter;
using Antlr4.Runtime;
using Rubberduck.Parsing.Grammar;
using Rubberduck.Parsing;

namespace Rubberduck.Refactorings.EncapsulateFieldInsertNewCode
{
    /// <summary>
    /// EncapsulateFieldInsertNewCodeRefactoringAction is a refactoring action dedicated to
    /// the insertion of new code content generated by EncapsulateField refactoring actions
    /// </summary>
    public class EncapsulateFieldInsertNewCodeRefactoringAction : CodeOnlyRefactoringActionBase<EncapsulateFieldInsertNewCodeModel>
    {
        private readonly IDeclarationFinderProvider _declarationFinderProvider;
        private readonly IPropertyAttributeSetsGenerator _propertyAttributeSetsGenerator;
        private readonly IEncapsulateFieldCodeBuilder _encapsulateFieldCodeBuilder;
        private readonly IIndenter _indenter;

        public EncapsulateFieldInsertNewCodeRefactoringAction(
            IDeclarationFinderProvider declarationFinderProvider, 
            IRewritingManager rewritingManager,
            IPropertyAttributeSetsGenerator propertyAttributeSetsGenerator,
            IEncapsulateFieldCodeBuilder encapsulateFieldCodeBuilder,
            IIndenter indenter)
                : base(rewritingManager)
        {
            _declarationFinderProvider = declarationFinderProvider;
            _propertyAttributeSetsGenerator = propertyAttributeSetsGenerator;
            _encapsulateFieldCodeBuilder = encapsulateFieldCodeBuilder;
            _indenter = indenter;
        }

        public override void Refactor(EncapsulateFieldInsertNewCodeModel model, IRewriteSession rewriteSession)
        {
            if (model.CreateNewObjectStateUDT)
            {
                CreateObjectStateUDTElements(model, rewriteSession);
            }

            CreateBackingFields(model, rewriteSession);

            CreatePropertyBlocks(model, rewriteSession);

            InsertBlocks(model, rewriteSession);
        }

        private void CreateObjectStateUDTElements(EncapsulateFieldInsertNewCodeModel model, IRewriteSession rewriteSession)
        {
            var objectStateFieldDeclaration = _encapsulateFieldCodeBuilder.BuildObjectStateFieldDeclaration(model.ObjectStateUDTField);
            model.NewContentAggregator.AddNewContent(NewContentType.DeclarationBlock, objectStateFieldDeclaration);

            var objectStateTypeDeclarationBlock = _encapsulateFieldCodeBuilder.BuildUserDefinedTypeDeclaration(model.ObjectStateUDTField, model.SelectedFieldCandidates);
            model.NewContentAggregator.AddNewContent(NewContentType.UserDefinedTypeDeclaration, objectStateTypeDeclarationBlock);
        }

        private void CreateBackingFields(EncapsulateFieldInsertNewCodeModel model, IRewriteSession rewriteSession)
        {
            foreach (var field in model.CandidatesRequiringNewBackingFields)
            {
                var newFieldDeclaration = _encapsulateFieldCodeBuilder.BuildFieldDeclaration(field.Declaration, field.BackingIdentifier);
                model.NewContentAggregator.AddNewContent(NewContentType.DeclarationBlock, newFieldDeclaration);
            }
        }

        private void CreatePropertyBlocks(EncapsulateFieldInsertNewCodeModel model, IRewriteSession rewriteSession)
        {
            var propAttributeSets = model.SelectedFieldCandidates
                .SelectMany(f => _propertyAttributeSetsGenerator.GeneratePropertyAttributeSets(f)).ToList();

            foreach (var propertyAttributeSet in propAttributeSets)
            {
                Debug.Assert(propertyAttributeSet.Declaration.DeclarationType.HasFlag(DeclarationType.Variable) || propertyAttributeSet.Declaration.DeclarationType.HasFlag(DeclarationType.UserDefinedTypeMember));

                var (Get, Let, Set) = _encapsulateFieldCodeBuilder.BuildPropertyBlocks(propertyAttributeSet);

                var blocks = new List<string>() { Get, Let, Set };
                blocks.ForEach(s => model.NewContentAggregator.AddNewContent(NewContentType.CodeSectionBlock, s));
            }
        }

        private void InsertBlocks(EncapsulateFieldInsertNewCodeModel model, IRewriteSession rewriteSession)
        {

            var newDeclarationSectionBlock = model.NewContentAggregator.RetrieveBlock(NewContentType.UserDefinedTypeDeclaration, NewContentType.DeclarationBlock, NewContentType.CodeSectionBlock);
            if (string.IsNullOrEmpty(newDeclarationSectionBlock))
            {
                return;
            }

            var allNewContent = string.Join(NewLines.DOUBLE_SPACE, newDeclarationSectionBlock);

            var previewMarker = model.NewContentAggregator.RetrieveBlock(RefactoringsUI.EncapsulateField_PreviewMarker);
            if (!string.IsNullOrEmpty(previewMarker))
            {
                allNewContent = $"{allNewContent}{Environment.NewLine}{previewMarker}";
            }

            var formattedContent = string.Join(Environment.NewLine, _indenter.Indent(allNewContent));

            var rewriter = rewriteSession.CheckOutModuleRewriter(model.QualifiedModuleName);

            var firstMember = _declarationFinderProvider.DeclarationFinder
                .Members(model.QualifiedModuleName)
                .Where(m => m.IsMember())
                .OrderBy(c => c.Selection)
                .FirstOrDefault();

            var codeSectionStartIndex = firstMember?.Context.Start.TokenIndex;

            var firstAnnotationContext = firstMember?.Annotations?
                .OrderBy(c => c.QualifiedSelection)
                .FirstOrDefault()?.Context;

            if (firstAnnotationContext != null)
            {
                codeSectionStartIndex = firstAnnotationContext
                    .GetAncestor<VBAParser.CommentOrAnnotationContext>().start.TokenIndex;
            }

            if (codeSectionStartIndex != null)
            {
                rewriter.InsertBefore(codeSectionStartIndex.Value, $"{formattedContent}{NewLines.DOUBLE_SPACE}");
            }
            else
            {
                var insertIndex = rewriter.TokenStream.Size - 1;
                rewriter.InsertBefore(insertIndex, $"{NewLines.DOUBLE_SPACE}{formattedContent}");
            }
        }
    }
}
